require 'test/unit'

$: << 'aerosol'
require 'aerosol/base/gamestate.rb'

class TestEventSource
	def initialize(*events)
		@events = events
	end
	
	def dequeue_events
		@events.each{|event| yield(event)}
		@events.clear
	end
	
	def push_events(*events)
		@events += events
	end
end

class GameStateTest < Test::Unit::TestCase
	def setup
		AS::GameState.event_source = TestEventSource.new
	end

  # records the sequence of method calls
  class SequenceDelegate
    attr_reader :updated

    def initialize(calls_arr, needs_update = false, needs_redraw = true)
      @needsRedraw  = needs_redraw
      @needsUpdate  = needs_update
      @updated = false
      @calls = calls_arr
    end
    
    def sync
      @calls << :sync
    end
    
    def input(event)
      #for testing purposes, just push the event onto the list.
      @calls << event
    end
    
    def needs_update
      @calls << :needs_update
      
      return @needsUpdate && !@updated
    end
    
    def update
      @calls << :update
      @updated = true
    end
    
    def needs_redraw
      @calls << :needs_redraw
      
      return @needsRedraw
    end
    
    def draw
      @calls << :draw
      
      AS::GameState.stop(:value1, :value2)
    end
    
    def enter_state
      @calls << :enter_state
    end
    
    def exit_state
      @calls << :exit_state
    end
  end
  
  # test that methods are called in the correct sequence
  def test_sync_sequence
    call_seq = []
    
    state = AS::GameState.new(SequenceDelegate.new(call_seq))
    
    # add some events to the virtual queue
    AS::GameState.event_source.push_events(:input1, :input2)
    value1, value2 = state.run
    
    # check the method call sequence
    expected = [
      :enter_state,
      :sync,
     :input1,
     :input2,
      :needs_update,
      :needs_redraw,
      :draw,
      :exit_state
    ]
    assert_equal(expected, call_seq)
    
    # check the return values
    assert_equal(:value1, value1)
    assert_equal(:value2, value2)
  end

  # test that methods are called in the correct sequence
  def test_sync_sequence_with_update
    call_seq = []
    
    state = AS::GameState.new(SequenceDelegate.new(call_seq, true, true))
    
    value1, value2 = state.run
    
    # check the method call sequence
    expected = [
      :enter_state,
      :sync,
      :needs_update,
      :update,
      :needs_update,
      :needs_redraw,
      :draw,
      :exit_state
    ]
    assert_equal(expected, call_seq)
    
    # check the return values
    assert_equal(:value1, value1)
    assert_equal(:value2, value2)
  end
 
 # test that methods are called in the correct sequence
  def test_sync_sequence_with_events
    call_seq = []

    state = AS::GameState.new(SequenceDelegate.new(call_seq))

    AS::GameState.event_source.push_events(:some_key, :some_key, :other_key)
    value1, value2 = state.run

    # check the method call sequence
    expected = [
      :enter_state,
      :sync,
      :some_key,
      :some_key,
      :other_key,
      :needs_update,
      :needs_redraw,
      :draw,
      :exit_state
    ]
    assert_equal(expected, call_seq)

    # check the return values
    assert_equal(:value1, value1)
    assert_equal(:value2, value2)
  end


 # This game state is the one we use for testing.
  class StateMachineTester < AS::GameState
    attr_writer :next_state
    
    def initialize(seq_arr, id)
      @seq, @id = seq_arr, id
      super()
    end
    
    # override loopstep for a quick early exit
    def loop_step
      @seq << @id
      
      # quick hack so that a state will only run twice
      next_state = @next_state
      @next_state = nil
      
      AS::GameState.stop(next_state)
    end
  end

  def test_state_machine
    state_seq = []
    
    state1 = StateMachineTester.new(state_seq, 1)
    state2 = StateMachineTester.new(state_seq, 2)
    state3 = StateMachineTester.new(state_seq, 3)
    
    state1.next_state = state2
    state2.next_state = state3
    state3.next_state = state2 # see note in StateMachineTester#loop_step
    
    AS::GameState.state_machine(state1)
    
    expected = [1, 2, 3, 2]
    assert_equal(expected, state_seq)
  end
  
  def test_tick_limited_state_helper(time_gap, expected_steps)
    call_seq = []

    state = AS::GameState::TickLimitedState.new(1.0, SequenceDelegate.new(call_seq))
    time = Time.now
    state.time_source = [time, time + time_gap].method(:shift)

    state.run

		# count the number of updates that occurred
		steps = call_seq.inject(0){|count, elt| elt == :update ? count + 1 : count}
    assert_equal(expected_steps, steps)
  end
 
 	# test the timing code in TickLimitedState
  def test_tick_limited_state
  	test_tick_limited_state_helper(0, 0)
  	test_tick_limited_state_helper(0.5, 1)
  	test_tick_limited_state_helper(2, 2)
  	test_tick_limited_state_helper(10.5, 11)
  end
end
